/// <reference path="Helpers.ts" />
/// <reference path="Types.ts" />
/// <reference path="messages/Assign.ts" />
/// <reference path="messages/AssociatEentities.ts" />
/// <reference path="messages/Create.ts" />
/// <reference path="messages/DisassociateEntities.ts" />
/// <reference path="messages/Error.ts" />
/// <reference path="messages/ExecuteWorkflow.ts" />
/// <reference path="messages/Organization.ts" />
/// <reference path="messages/Retrieve.ts" />
/// <reference path="messages/RetrieveEntity.ts" />
/// <reference path="messages/RetrieveMultiple.ts" />
/// <reference path="messages/SendEmail.ts" />
/// <reference path="messages/SetState.ts" />
/// <reference path="messages/Update.ts" />
/// <reference path="messages/WhoAmI.ts" />

module $Xrm {
    export class OrganizationService {
        private _sdkUrl: string;

        constructor(sdkUrl: string) {
            this._sdkUrl = sdkUrl;
        }

        Associate(moniker1: EntityReference, moniker2: EntityReference, relationshipName: string): void {
            var ar = new Messages.AssociateEntitiesRequest();
            ar.Moniker1 = moniker1;
            ar.Moniker2 = moniker2;
            ar.RelationshipName = relationshipName;

            this.Execute(ar);
        }

        AssociateAsync(moniker1: EntityReference, moniker2: EntityReference, relationshipName: string, callback?: () => void ): void {
            var ar = new Messages.AssociateEntitiesRequest();
            ar.Moniker1 = moniker1;
            ar.Moniker2 = moniker2;
            ar.RelationshipName = relationshipName;

            this.ExecuteAsync(ar,
                function (response: Messages.AssociateEntitiesResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(); }
                    }
                });
        }

        Disassociate(moniker1: EntityReference, moniker2: EntityReference, relationshipName: string): void {
            var ar = new Messages.DisassociateEntitiesRequest();
            ar.Moniker1 = moniker1;
            ar.Moniker2 = moniker2;
            ar.RelationshipName = relationshipName;

            this.Execute(ar);
        }

        DisassociateAsync(moniker1: EntityReference, moniker2: EntityReference, relationshipName: string, callback?: () => void ): void {
            var ar = new Messages.DisassociateEntitiesRequest();
            ar.Moniker1 = moniker1;
            ar.Moniker2 = moniker2;
            ar.RelationshipName = relationshipName;

            this.ExecuteAsync(ar,
                function (response: Messages.DisassociateEntitiesResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(); }
                    }
                });
        }

        Create(entity: Entity): string {
            var cr = new Messages.CreateRequest();
            cr.Target = entity;

            var response = <Messages.CreateResponse>this.Execute(cr);
            if (response != null && response != undefined) {
                return response.Id;
            }

            return null;
        }

        CreateAsync(entity: Entity, callback?: (id: string) => void ): void {
            var cr = new Messages.CreateRequest();
            cr.Target = entity;

            this.ExecuteAsync(cr,
                function (response: Messages.CreateResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(response.Id); }
                    }
                });
        }

        Update(entity: Entity): void {
            var ur = new Messages.UpdateRequest();
            ur.Target = entity;

            var response = this.Execute(ur);
        }

        UpdateAsync(entity: Entity, callback?: () => void ): void {
            var ur = new Messages.UpdateRequest();
            ur.Target = entity;

            this.ExecuteAsync(ur,
                function (response: Messages.CreateResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(); }
                    }
                });
        }

        Retrieve(entityName: string, id: string, columnSet: any): Entity {
            if (columnSet == null || columnSet == undefined) { columnSet = true; }

            var rr = new Messages.RetrieveRequest();
            rr.Target = new EntityReference(entityName, id);
            rr.ColumnSet = columnSet;

            var response = <Messages.RetrieveResponse>this.Execute(rr);
            if (response != null && response != undefined) {
                return response.Entity;
            }

            return null;
        }

        RetrieveAsync(entityName: string, id: string, columnSet: any, callback?: (entity: Entity) => void ) {
            if (columnSet == null || columnSet == undefined) { columnSet = true; }

            var rr = new Messages.RetrieveRequest();
            rr.Target = new EntityReference(entityName, id);
            rr.ColumnSet = columnSet;

            this.ExecuteAsync(rr,
                function (response: Messages.RetrieveResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(response.Entity); }
                    }

                });
        }

        RetrieveMultiple(fetchXml: string): Entity[] {
            var rmr = new Messages.RetrieveMultipleRequest();
            rmr.Query = new FetchExpression(this.Encode(fetchXml));

            var response = <Messages.RetrieveMultipleResponse>this.Execute(rmr);
            if (response != null && response != undefined) {
                return response.Entities;
            }

            return null;
        }

        RetrieveMultipleAsync(fetchXml: string, callback?: (entities: Entity[]) => void ) {
            var rmr = new Messages.RetrieveMultipleRequest();
            rmr.Query = new FetchExpression(this.Encode(fetchXml));

            this.ExecuteAsync(rmr,
                function (response: Messages.RetrieveMultipleResponse) {
                    if (response != null && response != undefined) {
                        if (callback != null) { callback(response.Entities); }
                    }
                });
        }

        // todo: convert these to DeleteRequest/Response messages to keep it consistant with others
        Delete(entityName: string, id: string): void {
            var request = this.ExecuteAjaxRequest("Delete", _string.format('<entityName>{0}</entityName><id>{1}</id>', entityName, id), false);
        }

        DeleteAsync(entityName: string, id: string, callback?: () => void ): void {
            this.ExecuteAjaxRequest("Delete", _string.format('<entityName>{0}</entityName><id>{1}</id>', entityName, id), true,
                function (request) {
                    if (callback != null) { callback(); }
                });
        }

        Execute(request: Messages.OrganizationRequest): Messages.OrganizationResponse {
            var response = this.ExecuteAjaxRequest("Execute", request.ToSoapXml(), false);
            if (response != null && response != undefined) {
                return request.GetResponse(response.responseXML);
            }

            return null;
        }

        ExecuteAsync(request: Messages.OrganizationRequest, callback?: (response: Messages.OrganizationResponse) => void ): void {
            var response = this.ExecuteAjaxRequest("Execute", request.ToSoapXml(), true,
                function (r) {
                    if (r != null && r != undefined) {
                        if (callback != null) { callback(request.GetResponse(r.responseXML)); }
                    }
                });
        }

        private WashWithSoapHeader(operation: string, requestXml: string): string {
            return _string.format('<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><{0} xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">{1}</{0}></s:Body></s:Envelope>', operation, requestXml);
        }

        private ExecuteAjaxRequest(operation: string, requestXml: string, async: bool, callback?: (request: XMLHttpRequest) => void ): XMLHttpRequest {
            requestXml = this.WashWithSoapHeader(operation, requestXml);
            var request = new XMLHttpRequest();

            request.open("POST", this._sdkUrl + "/XRMServices/2011/Organization.svc/web", async);
            request.setRequestHeader("Accept", "application/xml, text/xml, */*");
            request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
            request.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/" + operation);

            if (async) {
                request.onreadystatechange = function () {
                    if (request.readyState == 4) {
                        if (request.status == 200) {
                            callback(request);
                        } else {
                            throw new Messages.ErrorResponse(request.responseXML, request.responseText);
                        }
                    }
                };

                request.send(requestXml);
            }
            else {
                request.send(requestXml);

                if (request.status != 200) {
                    throw new Messages.ErrorResponse(request.responseXML, request.responseText);
                }

                return request;
            }

            return null;
        }

        private Encode(xml: string): string {
            return xml.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
        }
    }
}